iT邦幫忙

2021 iThome 鐵人賽

DAY 12
0

Select vs Switch

select與switch都是透過case的方式來處理,但兩者卻大不同,並完全不相容。

以下方code為舉例

package main

import "fmt"

var (
    i interface{}
)

func convert(i interface{}) {
    switch t := i.(type) {
    case int:
        fmt.Println("i is interger", t)
    case string:
        fmt.Println("i is string", t)
    case float64:
        fmt.Println("i is float64", t)
    default:
        fmt.Println("type not found")
    }
}

func main() {
    i = 100
    convert(i)
    i = float64(45.55)
    convert(i)
    i = "foo"
    convert(i)
    convert(float32(10.0))
}

運行後可得以下結果

i is interger 100
i is float64 45.55
i is string foo
type not found

switch可以配合各種類型與型別的接口操作,但是select只能接channel否則會出錯,default 會直接執行,所以沒有 default 的 select 就會遇到 blocking,假設沒有送 value 進去 Channel 就會造成 panic,底下拿幾個實際例子來解說。

Select

select使用的範例如下

package main

import "fmt"

func main() {
    ch := make(chan int, 1)

    ch <- 1
    select {
    case <-ch:
        fmt.Println("pull data")
    default:
        fmt.Println("default")
    }
}

運行後可得以下結果

pull data

Random Select

同一個channel在select會隨機選取,底下看個例子:

package main

import "fmt"

func main() {
    ch := make(chan int, 1)

    ch <- 1
    select {
    case <-ch:
        fmt.Println("random 01")
    case <-ch:
        fmt.Println("random 02")
    }
}

執行後會發現有時候拿到 random 01 有時候拿到 random 02,這就是 select 的特性之一,case 是隨機選取,所以當 select 有兩個 channel 以上時,如果同時對全部 channel 送資料,則會隨機選取到不同的 Channel。而上面有提到另一個特性『假設沒有送 value 進去 Channel 就會造成 panic』,拿上面例子來改:

package main

import "fmt"

func main() {
    ch := make(chan int, 1)

    select {
    case <-ch:
        fmt.Println("random 01")
    case <-ch:
        fmt.Println("random 02")
    }
}

執行後可得以下結果

atal error: all goroutines are asleep - deadlock!

goroutine 1 [select]:
main.main()
	/tmp/sandbox3304355956/prog.go:8 +0x65

執行後會發現變成 deadlock,造成 main 主程式爆炸,這時候可以直接用 default 方式解決此問題:

package main

import "fmt"

func main() {
    ch := make(chan int, 1)

    select {
    case <-ch:
        fmt.Println("random 01")
    case <-ch:
        fmt.Println("random 02")
    default:
        fmt.Println("exit")
    }
}

執行後可得以下結果

exit

主程式 main 就不會因為讀不到 channel value 造成整個程式 deadlock。

Timeout in select

用 select 讀取 channle 時,一定會實作超過一定時間後就做其他事情,而不是一直 blocking 在 select 內。底下是簡單的例子:

package main

import (
    "fmt"
    "time"
)

func main() {
    timeout := make(chan bool, 1)
    go func() {
        time.Sleep(2 * time.Second)
        timeout <- true
    }()
    ch := make(chan int)
    select {
    case <-ch:
    case <-timeout:
        fmt.Println("timeout 01")
    }
}

執行後可得以下結果

timeout 01

建立 timeout channel,讓其他地方可以透過 trigger timeout channel 達到讓 select 執行結束,也或者有另一個寫法是透握 time.After 機制

select {
    case <-ch:
    case <-timeout:
        fmt.Println("timeout 01")
    case <-time.After(time.Second * 1):
        fmt.Println("timeout 02")
    }

可以注意 time.After 是回傳 chan time.Time,所以執行 select 超過一秒時,就會輸出 timeout 02。

Checking size of channel

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int, 1)
    ch <- 1
    select {
    case ch <- 2:
        fmt.Println("channel value is", <-ch)
        fmt.Println("channel value is", <-ch)
    default:
        fmt.Println("channel blocking")
    }
}

運算後可得以下結果

channel blocking

先宣告 buffer size 為 1 的 channel,先丟值把 channel 填滿。這時候可以透過 select + default 方式來確保 channel 是否已滿,上面例子會輸出 channel blocking,我們再把程式改成底下

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int, 2)
    ch <- 1
    select {
    case ch <- 2:
        fmt.Println("channel value is", <-ch)
        fmt.Println("channel value is", <-ch)
    default:
        fmt.Println("channel blocking")
    }
}

運算後可得以下結果

channel value is 1
channel value is 2

把 buffer size 改為 2 後,就可以繼續在塞 value 進去 channel 了

select for loop

如果你有多個 channel 需要讀取,而讀取是不間斷的,就必須使用 for + select 機制來實現

package main

import (
    "fmt"
    "time"
)

func main() {
    i := 0
    ch := make(chan string, 0)
    defer func() {
        close(ch)
    }()

    go func() {
    LOOP:
        for {
            time.Sleep(1 * time.Second)
            fmt.Println(time.Now().Unix())
            i++

            select {
            case m := <-ch:
                println(m)
                break LOOP
            default:
            }
        }
    }()

    time.Sleep(time.Second * 4)
    ch <- "stop"
}

運行後可得以下結果

1257894001
1257894002
1257894003
1257894004

其實把 default 拿掉也可以達到目的

select {
case m := <-ch:
    println(m)
    break LOOP

當沒有值送進來時,就會一直停在 select 區段,所以其實沒有 default 也是可以正常運作的,而要結束 for 或 select 都需要透過 break 來結束,但是要在 select 區間直接結束掉 for 迴圈,只能使用 break variable 來結束,這邊是大家需要注意的地方。

Summary

這章節主要延續著Channel去講述select的使用方式,希望能讓大家在使用channel的同時可以避開許許多多的error。


上一篇
Day11 Channel
下一篇
Day13 Defer
系列文
fmt.Println("從零開始的Golang生活")30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言